package com.wk.dao.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ComparatorUtils;
import org.apache.commons.collections.comparators.ComparableComparator;
import org.apache.commons.collections.comparators.ComparatorChain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.wk.dao.IMongodbDAO;
import com.wk.model.Paginate;
import com.wk.mongo.ClassAnnotationUtils;

/**
 * 实现Mongodb通用数据操作.
 * @author chenxiangbai <br>
 * @version 1.0.0 2014-1-7 上午11:24:53 <br>
 * @see IMongodbDAO
 * @since JDK 1.6.0
 */
@Repository(value="MongodbDAO")
public final class MongodbDAO implements IMongodbDAO {
	
	/**
	 * Mongodb数据操作工具类..
	 */
	@Autowired
	MongoOperations operations;

	@Autowired
	private MongoTemplate  mongoTemplate;
	@Override
	public <T> T getPO(Class<T> cls, Serializable id) {
		return this.operations.findById(id, cls);
	}

	@Override
	public <T> List<T> getPOList(Class<T> cls) {	
		return this.operations.findAll(cls);
	}
	@Override
	public <T> T getPO(Class<T> cls, Criteria... queryParam) {
		/**
		 * 如果没有任何条件，返回空
		 */
		if(queryParam==null|| queryParam.length==0)
			return null;
		final Query query = new Query();	 
		for (Criteria criteria : queryParam) {
			if (criteria != null) {
				query.addCriteria(criteria);
			}
		} 
		return this.operations.findOne(query, cls);
	}

	@Override
	public <T> List<T> getPOListByIds(Class<T> cls, String[] ids) {
		if(ids==null || ids.length==0)
			return new ArrayList<T>();
		final Collection<String> idList = new ArrayList<String>();
		for (String id : ids) {
			idList.add(id);
		}
		return this.operations.find(Query.query(
				Criteria.where(ClassAnnotationUtils.getPrimaryKeyField(cls).getName()).in(idList)), cls);
	}

	@Override
	public <T> List<T> getPOList(Class<T> cls, Criteria... queryParam) {
		return this.getPOList(cls, null, null, queryParam);
	}

	/** 
	 * {@inheritDoc}
	 * org.springframework.data.domain.Sort.Order[], 
	 * org.springframework.data.mongodb.core.query.Criteria[]) 
	 * @author chenxiangbai 2014-1-7 上午11:49:44 <br>
	 */
	@Override
	public <T> List<T> getPOList(Class<T> cls, Paginate paginate, List<Order> orders, Criteria... queryParam) {
		/**
		 * 如果没有任何条件，返回空，如果不作处理，mongdb会查出所有的
		 */
		if(queryParam==null|| queryParam.length==0)
			return new  ArrayList<T>();
		final Query query = new Query();

		if (paginate != null) {
			query.skip(paginate.getPageIndex() * paginate.getPageSize());
			query.limit(paginate.getPageSize());
		} 
		
		if (orders != null && orders.size() > 0) {
			query.with(new Sort(orders));
		}
		for (Criteria criteria : queryParam) {
			if (criteria != null) {
				query.addCriteria(criteria);
			}
		}
		return this.operations.find(query, cls);
	}
	
	/**
	 * 返回数据集的分页对象，包括总记录数和总页数
	 * 
	 * @author yaoyasong 2014-12-18
	 */
	@Override
	public <T> Page<T> getPOPageList(Class<T> cls, Paginate paginate,
			List<Order> orders, Criteria... queryParam) {
		
//		if(queryParam==null || queryParam.length==0)
//			return null;
		final Query query = new Query();

		if (paginate != null) {
			query.skip(paginate.getPageIndex() * paginate.getPageSize());
			query.limit(paginate.getPageSize());
		} 
		
		if (orders != null && orders.size() > 0) {
			query.with(new Sort(orders));
		}
		if(queryParam != null) {//查所有
			for (Criteria criteria : queryParam) {
				if (criteria != null) {
					query.addCriteria(criteria);
				}
			}
		}
		long totalCount = this.getRowCount(cls, queryParam);
		Page<T> pageResult = new PageImpl<T>(this.operations.find(query, cls),null,totalCount);
		return pageResult;
	}

	@Override
	public <T> long getRowCount(Class<T> cls, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		return this.operations.count(query, cls);
	}

	@Override
	public <T> void saveOrUpdatePOList(List<T> boList) {
		for (T po : boList) {
			this.saveOrUpdatePO(po);
		}
	}

	@Override
	public <T> void saveOrUpdatePO(T po) {
		this.operations.save(po);
	}

	@Override
	public <T> void saveOrUpdateDocument(Class<T> cls, String propertyName, Object propertyValue,
			Criteria... queryParam) {
		final Map<String, Object> updatePropertys = new HashMap<String, Object>();
		updatePropertys.put(propertyName, propertyValue);
		this.saveOrUpdateDocument(cls, updatePropertys, queryParam);
	}

	@Override
	public <T> void saveOrUpdateDocument(Class<T> cls, Map<String, Object> updatePropertys, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		
		Update update = new Update();
		for (Entry<String, Object> updateProperty : updatePropertys.entrySet()) {  
			update.set(updateProperty.getKey(), updateProperty.getValue());
		  }  
		 
		this.operations.updateMulti(query, update, cls);
	}
	
	@Override
	public <T> void UpdateUnsetDocument(Class<T> cls, List<String> unsetPropertys, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		
		Update update = new Update();
		for (String updateProperty : unsetPropertys) {  
			update.unset(updateProperty);
		  }   
		this.operations.updateMulti(query, update, cls);
	}

	@Override
	public <T> void deleteDocument(Class<T> cls, String propertyName, Criteria... queryParam) {
		final String[] propertyNames = new String[] { propertyName };
		this.deleteDocument(cls, propertyNames, queryParam);
	}

	@Override
	public <T> void deleteDocument(Class<T> cls, String[] propertyNames, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		
		Update update = new Update();
		for (String propertyName : propertyNames) {
			update.set(propertyName, null);
		}

		this.operations.updateMulti(query, update, cls);
		
	}

	@Override
	public <T> void saveArray(Class<T> cls, String propertyName, Object value, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		Update update = new Update(); 
		update.push(propertyName, value);
		this.operations.upsert(query, update, cls);
		 
	}

	@Override
	public <T> void deleteArray(Class<T> cls, String propertyName, Object value, Criteria... queryParam) {
	 	
		 final Query query = createQuery(queryParam);
		Update update = new Update(); 
		update.pull(propertyName, value);
	
		this.operations.updateMulti(query, update, cls);		
	}

	@Override
	public <T> void deleteArray(Class<T> cls, String propertyName, Object[] values, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		Update update = new Update(); 		 
		update.pullAll(propertyName, values);
		this.operations.upsert(query, update, cls);		 
	}

	@Override
	public <T> void saveOrUpdateObjectArray(Class<T> cls, String propertyName, Object value, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		Update update = new Update(); 
		update.set(propertyName, value);
		this.operations.upsert(query, update, cls);
		
	}

	@Override
	public <T> void deleteObjectArray(Class<T> cls, String propertyName, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		Update update = new Update(); 
		update.unset(propertyName);
		this.operations.upsert(query, update, cls);		
		
	}

	@Override
	public <T> void updateInc(Class<T> cls, String propertyName,int inc, Criteria... queryParam) {
		final Query query = createQuery(queryParam);
		Update update = new Update(); 
		update.inc(propertyName, inc); 
		this.operations.updateMulti(query, update, cls);		
	}

	private Query createQuery(Criteria... queryParam) {
		final Query query = new Query();
		if(queryParam != null) {
			for (Criteria criteria : queryParam) {
				if (criteria != null) {
					// qiushaohua@2014-03-06 增加判断
					query.addCriteria(criteria);
				}
			}
		}
		return query;
	}

	@Override
	public <T> void deletePO(T po) {
		this.operations.remove(po);
	}

	@Override
	public <T> void deletePO(Class<T> cls, Serializable id) {
		this.operations.remove(new Query(Criteria.where(ClassAnnotationUtils.getPrimaryKeyField(cls).getName()).is(id)), cls);		
	}

	@Override
	public <T> void deletePOList(Class<T> cls, List<T> poList) {
		for (T po : poList) {
			this.deletePO(po);
		}
	}

	@Override
	public <T> void deletePOList(Class<T> cls, Criteria... queryParam) {
		final Query query = createQuery(queryParam);		
		this.operations.remove(query, cls);		
	}
	@Override
	public <T> List<T> getPoDistinct(String collectionName,String key,final Criteria... queryParam) {
		/**
		 * 如果没有任何条件，返回空，如果不作处理，mongdb会查出所有的
		 */
		if(queryParam==null|| queryParam.length==0)
			return new  ArrayList<T>();
		Query query = createQuery(queryParam); 	 
		return  this.operations.getCollection(collectionName).distinct(key, query.getQueryObject());
	}
	 
	public void get(String collectionName,String key,String value){
		/* 创建 $unwind 操作, 用于切分数组*/ 
		DBObject match = new BasicDBObject("$match", new BasicDBObject(key, value));  
		 DBObject group = new BasicDBObject("$group", new BasicDBObject("userSelects.selectNum", -1));
		 AggregationOutput output = this.operations.getCollection(collectionName).aggregate(match, group); 
	}

	@Override
	public <T> List<T> unionList(final List<Order> orderList, final int count, final List<T>... list) {
        final List<T> sortList = new ArrayList<T>();       
        for (List<T> listTemp : list) {
        	sortList.addAll(listTemp);
		}
  
        // 在合并的集合的数量大于1个时才排序
        if (sortList.size() > 1) {
	        // 声明要排序的对象的属性，并指明所使用的排序规则，如果不指明，则用默认排序   
	        final ArrayList<Object> sortFields = new ArrayList<Object>();     
	        if(CollectionUtils.isNotEmpty(orderList)){
	        	for (Order order : orderList) {
		        	// 创建一个排序规则   
		            Comparator comparator = ComparableComparator.getInstance();       
		            comparator = ComparatorUtils.nullLowComparator(comparator);  
		            if (!order.isAscending()) {
		            	comparator = ComparatorUtils.reversedComparator(comparator);
		            } 
		            sortFields.add(new BeanComparator(order.getProperty(), comparator));
				}
	        	
	        	 // 创建一个排序链   
		        final ComparatorChain multiSort = new ComparatorChain(sortFields);    
		        // 开始真正的排序
		        Collections.sort(sortList, multiSort); 
	        }
	        
	        if (sortList.size() > count && count>0) {
	        	return sortList.subList(0, count);
	        } else {
	        	return sortList;
	        }
        } else {
        	return sortList;
        }
	}

	@Override
	public <T> List<BasicDBObject> groupBy(Class<T> cls,String propertyName, Criteria... queryParam) {
		Criteria criteria =null;
		if(queryParam != null){
			criteria = new Criteria();
				criteria.andOperator(queryParam);
		}
		 GroupBy groupBy = GroupBy.key(propertyName).initialDocument("{count:0}").reduceFunction("function(doc, prev){prev.count+=1}");
		 GroupByResults resluts = operations.group(criteria,operations.getCollectionName(cls), groupBy,cls);
		 if(resluts != null){
			 List<BasicDBObject> list = (List<BasicDBObject>) resluts.getRawResults().get("retval");	
			 return list;
		 }
		  return null;
	}
	
	@Override
    public <T> GeoResults<T> findByAddressLocationNear (Point point, Distance distance,Class<T> cls) {
        return this.operations.geoNear(NearQuery.near(point).maxDistance(distance), cls);
    }

	@Override
	public <T> GeoResults<T> findByLocationNear(NearQuery query,
			Class<T> cls) {
		return this.operations.geoNear(query, cls);
	}

}
